// Tencent is pleased to support the open source community by making
// 蓝鲸智云 - 监控平台/日志平台 (BlueKing - Monitor/Log) available.
// Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
// Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://opensource.org/licenses/MIT
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//

package testutils

import (
	"context"
	"math/rand"
	"time"

	"go.opentelemetry.io/collector/model/pdata"

	"github.com/TencentBlueKing/bk-collector/define"
)

func FakeTraceID() [16]byte {
	b := make([]byte, 16)
	rand.Read(b)

	ret := [16]byte{}
	for i := 0; i < 16; i++ {
		ret[i] = b[i]
	}
	return ret
}

func FakeSpanID() [8]byte {
	b := make([]byte, 8)
	rand.Read(b)

	ret := [8]byte{}
	for i := 0; i < 8; i++ {
		ret[i] = b[i]
	}
	return ret
}

func FakeAttributeMap(keys []string, valueType string) pdata.AttributeMap {
	m := pdata.NewAttributeMap()
	for _, key := range keys {
		switch valueType {
		case "int":
			m.UpsertInt(key, rand.Int63())
		case "bool":
			m.UpsertBool(key, rand.Int31()%2 == 0)
		case "float":
			m.UpsertDouble(key, rand.Float64())
		default:
			m.UpsertString(key, FakeString(24))
		}
	}
	return m
}

func FakeString(n int) string {
	letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._")

	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

type TracesGenerator struct {
	ctx    context.Context
	cancel context.CancelFunc
	opts   define.TracesOptions

	attributes pdata.AttributeMap
	resources  pdata.AttributeMap
}

func NewTracesGenerator(opts define.TracesOptions) *TracesGenerator {
	attributes := FakeAttributeMap(opts.RandomAttributeKeys, opts.DimensionsValueType)
	resources := FakeAttributeMap(opts.RandomResourceKeys, opts.DimensionsValueType)
	ctx, cancel := context.WithCancel(context.Background())
	return &TracesGenerator{
		ctx:        ctx,
		cancel:     cancel,
		attributes: attributes,
		resources:  resources,
		opts:       opts,
	}
}

func (g *TracesGenerator) Stop() {
	g.cancel()
}

func (g *TracesGenerator) Ch() chan pdata.Traces {
	ch := make(chan pdata.Traces, 128)
	data := g.Generate()
	n := 0
	go func() {
		defer close(ch)
		for {
			select {
			case <-g.ctx.Done():
				return

			case ch <- data:
				n++
				if n >= g.opts.Iteration {
					return
				}
				time.Sleep(g.opts.Interval)
			}
		}
	}()
	return ch
}

func (g *TracesGenerator) Generate() pdata.Traces {
	td := pdata.NewTraces()
	rs := td.ResourceSpans().AppendEmpty()
	rs.Resource().Attributes().UpsertString("service.name", "benchmark.service")
	g.resources.CopyTo(rs.Resource().Attributes())
	for k, v := range g.opts.Resources {
		rs.Resource().Attributes().UpsertString(k, v)
	}

	now := time.Now()
	for i := 0; i < g.opts.SpanCount; i++ {
		span := rs.InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty()
		span.SetName(FakeString(12))
		span.SetSpanID(pdata.NewSpanID(FakeSpanID()))
		span.SetTraceID(pdata.NewTraceID(FakeTraceID()))
		span.SetStartTimestamp(pdata.NewTimestampFromTime(now))
		span.SetEndTimestamp(pdata.NewTimestampFromTime(now.Add(time.Second)))
		span.SetKind(pdata.SpanKind(g.opts.SpanKind))
		g.attributes.CopyTo(span.Attributes())
		for k, v := range g.opts.Attributes {
			span.Attributes().UpsertString(k, v)
		}
	}
	return td
}
